/**
 * Copyright 2011 Mathieu Magnaudet
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package edu.enac.geoapp.gui.component;

import java.awt.Color;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.Map;
import java.util.Set;

import javax.swing.BorderFactory;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.ScrollPaneConstants;

import org.geotools.geometry.DirectPosition2D;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.jts.JTSFactoryFinder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.map.MapLayer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.swing.JMapPane;
import org.geotools.swing.event.MapMouseAdapter;
import org.geotools.swing.event.MapMouseEvent;
import org.geotools.swing.tool.PanTool;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.Point;

import edu.enac.geoapp.services.CRSProvider;
import edu.enac.geoapp.services.DataProvider;

/**
 * @author mathieu Component containing the map and the various layers of the
 *         application
 * 
 */
@SuppressWarnings("serial")
public class MapComponent extends Component implements MouseWheelListener {
	private static final double ZOOM_FACTOR = 2;
	private JTextField info = new JTextField();
	final JMapPane mapPane;
	private MapContext mapContext;
	private DirectPosition2D mapPos = new DirectPosition2D();
	private ReferencedEnvelope defaultReferenceEnvelope;
	private Popup popup;
	private JTextArea field;
	private Map<String, Point> airportMap;
	private Set<String> airportsSet;
	protected GeometryFactory geometryFactory = JTSFactoryFinder
			.getGeometryFactory(null);
	final PopupFactory factory = PopupFactory.getSharedInstance();

	/**
	 * @return the info
	 */
	public JTextField getInfo() {
		return info;
	}

	public MapComponent() {
		final DataProvider dataProvider = DataProvider.getInstance();
		airportMap = dataProvider.getAirports();
		airportsSet = airportMap.keySet();

		mapPane = new JMapPane();
		mapPane.setBackground(new Color(157, 201, 255));

		field = new JTextArea("test", 10, 50);
		field.setWrapStyleWord(true);
		field.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));

		CRSProvider provider = CRSProvider.getInstance();
		defaultReferenceEnvelope = new ReferencedEnvelope(-13.9, 13.6, 58.6,
				38.2, provider.getStandardCRS());
		try {
			mapContext = new DefaultMapContext();
			mapContext.setTitle("Projet");

			mapPane.setMapContext(mapContext);
			mapPane.addMouseMotionListener(new MouseMotionListener() {
				// retrieving the latitude and longitude corresponding to the
				// mouse position on the screen
				@Override
				public void mouseMoved(MouseEvent e) {
					try {
						AffineTransform tr = mapPane
								.getScreenToWorldTransform();
						Point2D ptDst = tr.transform(
								mapPane.getMousePosition(true), null);
						info.setText("long." + ptDst.getX() + " lat. "
								+ ptDst.getY());
					} catch (NullPointerException ex) {

					}
				}

				@Override
				public void mouseDragged(MouseEvent e) {
					// TODO Auto-generated method stub

				}
			});

			// Pop-up management for TAF and METAR
			mapPane.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent e) {
					if (e.getButton() == MouseEvent.BUTTON3) {
						if (popup != null)
							popup.hide();
						String airport = getNearestAirport();
						StringBuffer buff = new StringBuffer(airport);
						buff.append("\nMETAR:\n");
						buff.append(dataProvider.getMetar(airport));
						buff.append("\nTAF:\n");
						buff.append(dataProvider.getTar(airport));

						field.setText(buff.toString());
						JScrollPane scrollingResult = new JScrollPane(field);
						scrollingResult
								.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
						scrollingResult
								.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
						popup = factory.getPopup(mapPane, scrollingResult,
								e.getX(), e.getY());
						popup.show();
					}
					if (e.getButton() == MouseEvent.BUTTON1) {
						if (popup != null)
							popup.hide();
					}
				}

			});

			StreamingRenderer renderer = new StreamingRenderer();
			mapPane.setRenderer(renderer);
			mapPane.setDisplayArea(defaultReferenceEnvelope);
			mapPane.setCursorTool(new PanTool());
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(0);
		}

		mapPane.addMouseWheelListener(this);
		mapPane.addMouseListener(new MapMouseAdapter() {
			@Override
			public void onMouseMoved(MapMouseEvent ev) {
				mapPos = ev.getMapPosition();
			}
		});
	}

	public void addLayer(MapLayer layer) {
		mapContext.addLayer(layer);
	}

	public void removeLayer(MapLayer layer) {
		mapContext.removeLayer(layer);
	}

	public void removeAllLayers() {
		mapContext.removeLayers(mapContext.getLayers());
	}

	public void resetDefaultEnvelope() {
		mapPane.setDisplayArea(defaultReferenceEnvelope);
	}

	/**
	 * @return the mapPane
	 */
	public JMapPane getMapPane() {
		return mapPane;
	}

	/**
	 * Zoom in and zoom out 
	 */
	@Override
	public void mouseWheelMoved(MouseWheelEvent arg0) {
		try {
			Rectangle paneArea = mapPane.getVisibleRect();
			double scale = mapPane.getWorldToScreenTransform().getScaleX();
			double newScale;

			if (arg0.getWheelRotation() == -1) {// zoom in
				newScale = scale * ZOOM_FACTOR;
			} else {// zoom out
				newScale = scale / ZOOM_FACTOR;
			}

			DirectPosition2D corner = new DirectPosition2D(mapPos.getX() - 0.5d
					* paneArea.getWidth() / newScale, mapPos.getY() + 0.5d
					* paneArea.getHeight() / newScale);
			Envelope2D newMapArea = new Envelope2D();
			newMapArea.setFrameFromCenter(mapPos, corner);
			mapPane.setDisplayArea(newMapArea);
		} catch (NullPointerException e) {

		}

	}

	/**
	 * 
	 * @return the latitude corresponding to the mouse position
	 */
	public double getLat() {
		AffineTransform tr = mapPane.getScreenToWorldTransform();
		Point2D ptDst = tr.transform(mapPane.getMousePosition(true), null);
		return ptDst.getY();
	}

	/**
	 * 
	 * @return the longitude corresponding to the mouse position
	 */
	public double getLon() {
		AffineTransform tr = mapPane.getScreenToWorldTransform();
		Point2D ptDst = tr.transform(mapPane.getMousePosition(true), null);
		return ptDst.getX();
	}

	/**
	 * 
	 * @param text Set the content of the popup
	 */
	public void setPopUpContent(String text) {
		field.setText(text);
	}

	/**
	 * 
	 * @return The nearest airport from the current mouse position 
	 */
	private String getNearestAirport() {
		Point p = geometryFactory
				.createPoint(new Coordinate(getLat(), getLon()));

		String nearest = "";
		double distance = Double.MAX_VALUE;
		for (String airport : airportsSet) {
			double newDistance = getDistance(p, airportMap.get(airport));
			if (newDistance < distance) {
				distance = newDistance;
				nearest = airport;
			}
		}
		return nearest;
	}

	/**
	 * 
	 * @param a First point
	 * @param b Second point
	 * @return The distance between the given points
	 */
	
	private double getDistance(Point a, Point b) {
		return Math.sqrt(sqr(a.getX() - b.getX()) + sqr(a.getY() - b.getY()));
	}

	/**
	 *
	 * @param d
	 * @return the square
	 */
	private double sqr(double d) {
		return d * d;
	}
}